<template>
  <div class="layout-container">
    <!-- 侧边栏 -->
    <SideBar @tab-change="handleTabChange" />
    
    <!-- 主内容区 -->
    <div class="main-content">
      <div class="main-header">
        <span class="main-title">AI对话</span>
      </div>
      <div class="main-divider"></div>
      
      <div class="main-body">
        <!-- 数字人视频显示区域 -->
        <div class="digital-human-section">
          <!-- 视频模式 -->
          <div v-if="!isAudioOnly" class="avatar-container show-loader" :class="{ 'video-ready': idleVideoUrl || currentFrameData }">
            <div v-if="!idleVideoUrl && !currentFrameData" class="avatar-placeholder">
              <div class="placeholder-text">{{ avatarStatusText }}</div>
            </div>
            <!-- 视频元素 -->
            <video
              ref="avatarVideo"
              :poster="relativeInfo.avatar"
              autoplay
              playsinline
              style="width: 125%; height: 125%; object-fit: cover; border-radius: 50%; transform: translate(-3%, 7%) scale(1.2);"
              :style="{ 
                visibility: (idleVideoUrl || currentFrameData) ? 'visible' : 'hidden',
                opacity: (idleVideoUrl || currentFrameData) ? 1 : 0
              }"
            />
            
            <!-- 连接状态指示器 -->
            <div class="connection-status" :class="{ connected: liteAvatarConnected }">
              <div class="status-dot"></div>
              <span class="status-text">{{ avatarStatusText }}</span>
            </div>
          </div>

          <!-- 语音模式 -->
          <div v-else class="avatar-container audio-only">
            <img :src="relativeInfo.avatar" alt="relative" class="digital-human-video" />
          </div>
          
          <!-- 错误状态显示 -->
          <div v-if="hasError" class="error-status">
            <div class="error-message">{{ errorMessage }}</div>
            <button class="retry-btn" @click="retryConnection" :disabled="isInitializing">
              {{ isInitializing ? '重试中...' : '重试连接' }}
            </button>
          </div>
        </div>

        <!-- 聊天记录区域 -->
        <div class="chat-history" :class="{ expanded }">
          <div class="chat-controls">
            <button class="expand-toggle" @click="expanded = !expanded">
              {{ expanded ? '收起对话' : '展开对话' }}
            </button>
          </div>
          <div class="messages-container" ref="messagesContainer">
            <div 
              v-for="message in orderedMessages" 
              :key="message.id"
              class="message"
              :class="message.type"
            >
              <span class="message-dot"></span>
              <div class="message-content">
                <!-- 视频合成中的加载动画 -->
                <div v-if="message.isWaitingVideo" class="video-loading">
                  <div class="loading-dots">
                    <span></span>
                    <span></span>
                    <span></span>
                  </div>
                </div>
                <!-- 正常文字内容 -->
                <span v-else>{{ message.displayText !== undefined ? message.displayText : message.text }}</span>
              </div>
            </div>
            
            <!-- AI正在输入提示 -->
            <div v-if="isAITyping" class="message ai typing">
              <div class="message-content">
                <div class="typing-indicator">
                  <span></span>
                  <span></span>
                  <span></span>
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- 控制按钮区域 -->
        <div class="controls">
          <button class="control-btn end-call-btn" @click="endCall">
            <uni-icons type="phone" size="24" color="#fff" />
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount, nextTick, onUnmounted, watch } from 'vue'
import { useRouter, onBeforeRouteLeave, useRoute } from 'vue-router'
import { go } from '@/utils/nav.js'
import { useUserStore } from '@/store'
import { useRelativeStore } from '@/store/modules/relative'
import SideBar from '/pages/pc/sideBar.vue'
import { startAgent, stopAgent, ping } from '@/api/pc/ten'
import { getRelativeProfile, getChatHistory } from '@/api/pc/elderly'
// 修复导入 - 使用默认导入
import request from '@/utils/request'
import { rtcManager } from '@/plugins/rtc'
import { rtmManager } from '@/plugins/rtm'
import config from '@/config'
import { getRTCToken } from '@/api/pc/ten'
// ============ LiteAvatar 导入 ============
import { LiteAvatarClient } from '@/utils/liteAvatarClient'
import { AudioToPCMConverter } from '@/utils/audioConverter'

const router = useRouter() // 仅用于路由守卫等；页面跳转统一用 go()
const route = useRoute() // 用于获取路由参数
const userStore = useUserStore()
const relativeStore = useRelativeStore()

// 响应式数据
const connected = ref(false)
const messages = ref([])
const isAITyping = ref(false)
const microphoneEnabled = ref(true)
const errorMessage = ref('')
const messagesContainer = ref(null)
// 🎯 直接从路由参数初始化，使用空字符串避免闪现
const relativeInfo = ref({
  name: route.query.roleName || '亲属',
  relationship: route.query.roleRelationship || '家人',
  avatar: route.query.avatar || '',  // 使用空字符串，避免显示默认图片
  fullRoleData: null // 存储完整的 role 数据
})
const expanded = ref(false)

// ============ LiteAvatar 相关变量 ============
const avatarImage = ref(null) // img 元素引用（逐帧模式）
const avatarVideo = ref(null) // video 元素引用（视频片段模式）
const currentFrameData = ref('') // 当前显示的帧（Base64）
const liteAvatarConnected = ref(false) // LiteAvatar 连接状态
let liteAvatarClient = null // LiteAvatar 客户端实例
let audioConverter = null // 音频转换器实例

// 🎬 流式播放管理
const idleVideoUrl = ref('') // 待机视频URL（循环播放）
const isPlayingIdleVideo = ref(true) // 是否正在播放待机视频
let currentSpeechVideoUrl = null // 当前语音视频URL
let isPlayingSpeechVideo = false // 是否正在播放语音视频

// ============ Trulience 相关变量（已注释） ============
// const trulienceConnected = ref(false)
// let trulience = null

const avatarStatusText = ref('初始化中...')
const isInitializing = ref(false)
const hasError = ref(false)
const hasSyncedAudio = ref(false)
let remoteAudioTrack = null
let remoteAudioPollTimer = null

// 打字机效果相关状态
const typingMessages = ref(new Map()) // 存储正在打字的消息
const typingTimers = ref(new Map()) // 存储打字定时器

// 通话类型（默认 audio），在挂载时根据路由设定
const callType = ref('audio')
// 是否仅语音模式（不加载数字人、不做口型同步）
const isAudioOnly = computed(() => String(callType.value || 'audio').toLowerCase().trim() !== 'video')

// ============ Trulience SDK 实例（已注释） ============
// let trulience = null

let heartbeatTimer = null
let chatHistoryTimer = null

// RTC 聊天服务现在复用现有的 RTC 连接，不需要单独的实例

// 状态管理变量
const isCleaningUp = ref(false)
const isInitialized = ref(false)

// LiteAvatar 初始化
const initLiteAvatar = async () => {
  try {
    await nextTick()
    avatarStatusText.value = '连接数字人服务...'
    
    await new Promise(resolve => setTimeout(resolve, 300))
    
    // 创建客户端，使用video模式
    liteAvatarClient = new LiteAvatarClient('video')
    
    // 待机视频回调
    liteAvatarClient.onIdle((idleBlob) => {
      console.log(`Received idle video: ${idleBlob.size} bytes`)
      
      // 释放旧的待机视频URL
      if (idleVideoUrl.value && idleVideoUrl.value.startsWith('blob:')) {
        URL.revokeObjectURL(idleVideoUrl.value)
      }
      
      // 创建新的待机视频URL
      idleVideoUrl.value = URL.createObjectURL(idleBlob)
      
      // 如果当前没有播放语音视频，立即播放待机视频
      if (!isPlayingSpeechVideo) {
        playIdleVideo()
      }
    })
    
    // 语音视频回调
    liteAvatarClient.onVideo((videoBlob, duration, frames) => {
      console.log(`Received speech video: ${duration}s, ${frames} frames`)
      playSpeechVideo(videoBlob)
    })
    
    // 播放待机视频
    const playIdleVideo = () => {
      let video = avatarVideo.value
      
      if (!video || typeof video.play !== 'function') {
        console.warn('Video ref not available, querying DOM')
        video = document.querySelector('.avatar-container video')
      }
      
      if (!video || !idleVideoUrl.value) {
        console.warn('Cannot play idle video')
        return
      }
      
      console.log('Playing idle video (loop)')
      
      try {
        video.src = idleVideoUrl.value
        video.loop = true
        video.muted = true
        
        video.play().then(() => {
          console.log('Idle video playing')
          isPlayingIdleVideo.value = true
          isPlayingSpeechVideo = false
          
          const avatarContainer = document.querySelector('.avatar-container')
          if (avatarContainer) {
            avatarContainer.classList.add('video-loaded')
          }
        }).catch(err => {
          console.error('Failed to play idle video:', err)
        })
      } catch (err) {
        console.error('Error setting video src:', err)
      }
    }
    
    // 播放语音视频
    const playSpeechVideo = (videoBlob) => {
      let video = avatarVideo.value
      
      if (!video || typeof video.play !== 'function') {
        console.warn('Video ref not available, querying DOM')
        video = document.querySelector('.avatar-container video')
      }
      
      if (!video) {
        console.warn('Cannot play speech video')
        return
      }
      
      console.log('Switching to speech video')
      
      if (currentSpeechVideoUrl && currentSpeechVideoUrl.startsWith('blob:')) {
        URL.revokeObjectURL(currentSpeechVideoUrl)
      }
      
      currentSpeechVideoUrl = URL.createObjectURL(videoBlob)
      
      try {
        video.src = currentSpeechVideoUrl
        video.loop = false
        video.muted = false
        
        // 播放结束回到待机视频
        const onEnded = () => {
          console.log('Speech video ended, returning to idle')
          video.removeEventListener('ended', onEnded)
          
          // 释放语音视频URL
          if (currentSpeechVideoUrl && currentSpeechVideoUrl.startsWith('blob:')) {
            URL.revokeObjectURL(currentSpeechVideoUrl)
            currentSpeechVideoUrl = null
          }
          
          // 回到待机视频
          playIdleVideo()
        }
        
        video.addEventListener('ended', onEnded)
        
        // 播放语音视频
        video.play().then(() => {
          console.log('Speech video playing')
          isPlayingIdleVideo.value = false
          isPlayingSpeechVideo = true
          
          // 视频播放时启动打字机效果
          nextTick(() => {
            for (let i = messages.value.length - 1; i >= 0; i--) {
              const message = messages.value[i]
              if (message.type === 'ai' && 
                  message.isFinal && 
                  message.text && 
                  message.text.trim().length > 0 &&
                  (!message.displayText || message.displayText === '')) {
                console.log('Starting typing effect for message:', message.id)
                // 取消等待视频状态
                message.isWaitingVideo = false
                startTypingEffect(message.id, message.text, 120)
                break
              }
            }
          })
        }).catch(err => {
          console.error('Failed to play speech video:', err)
          playIdleVideo()
        })
      } catch (err) {
        console.error('Error setting video src:', err)
        playIdleVideo()
      }
    }
    
    // 状态回调
    liteAvatarClient.onStatus((status) => {
      console.log('LiteAvatar status:', status)
      
      if (status === 'connected') {
        avatarStatusText.value = '正在初始化数字人...'
        liteAvatarConnected.value = true
      } else if (status === 'initialized') {
        avatarStatusText.value = '数字人已就绪'
        liteAvatarConnected.value = true
        
        // 标记视频已加载
        const avatarContainer = document.querySelector('.avatar-container')
        if (avatarContainer) {
          avatarContainer.classList.add('video-loaded')
        }
      } else if (status === 'error') {
        avatarStatusText.value = '连接失败'
        liteAvatarConnected.value = false
        
        const avatarContainer = document.querySelector('.avatar-container')
        if (avatarContainer) {
          avatarContainer.classList.remove('video-loaded')
        }
      } else if (status === 'disconnected') {
        avatarStatusText.value = '连接已断开'
        liteAvatarConnected.value = false
        
        const avatarContainer = document.querySelector('.avatar-container')
        if (avatarContainer) {
          avatarContainer.classList.remove('video-loaded')
        }
      }
    })
    
    // 错误回调
    liteAvatarClient.onError((error) => {
      console.error('LiteAvatar error:', error)
      hasError.value = true
      errorMessage.value = '数字人服务错误: ' + (error.message || error)
    })
    
    // 连接到 WebSocket 服务
    await liteAvatarClient.connect(config.liteAvatar.videoServiceUrl)
    
    const roleGender = relativeInfo.value.fullRoleData?.gender || '男'
    const avatarId = getAvatarIdByGender(roleGender)
    
    console.log(`Initializing avatar for gender: ${roleGender}, avatarId: ${avatarId}`)
    
    await liteAvatarClient.initAvatar(avatarId)
    
    console.log('LiteAvatar initialized successfully')
    
  } catch (error) {
    console.error('Failed to initialize LiteAvatar:', error)
    avatarStatusText.value = '数字人初始化失败: ' + (error.message || '未知错误')
    hasError.value = true
    errorMessage.value = '无法连接到数字人服务'
    throw error
  }
}

// 根据性别选择 Avatar ID
const getAvatarIdByGender = (gender) => {
  const avatarIds = {
    '男': 'P1d5LAiX3CqPUMXODP75gxiQ',
    '女': 'P1_Eg2LyIHmy5q7jwGgloy3A',
    '0': 'P1d5LAiX3CqPUMXODP75gxiQ',
    '1': 'P1_Eg2LyIHmy5q7jwGgloy3A'
  }
  console.log(`Selecting avatar for gender "${gender}": ${avatarIds[gender] || avatarIds['男']}`)
  return avatarIds[gender] || avatarIds['男']
}

// 帧加载错误处理
const handleFrameError = (event) => {
  console.error('Frame failed to load:', {
    src: currentFrameData.value,
    error: event,
    imgElement: avatarImage.value
  })
  const avatarContainer = document.querySelector('.avatar-container')
  if (avatarContainer) {
    avatarContainer.classList.remove('video-loaded')
  }
}

// 🚀 处理帧加载成功（二进制传输调试）
const handleFrameLoaded = () => {
  console.log('✅ Frame loaded successfully, src:', currentFrameData.value.substring(0, 50) + '...')
  const avatarContainer = document.querySelector('.avatar-container')
  if (avatarContainer) {
    avatarContainer.classList.add('video-loaded')
  }
}

// ============ Trulience 视频处理（已注释） ============
// const handleVideoError = () => {
//   avatarStatusText.value = '视频加载失败'
//   const avatarContainer = document.querySelector('.avatar-container')
//   if (avatarContainer) {
//     avatarContainer.classList.remove('video-loaded')
//   }
// }

// const handleVideoLoaded = () => {
//   const avatarContainer = document.querySelector('.avatar-container')
//   if (avatarContainer) {
//     avatarContainer.classList.add('video-loaded')
//   }
// }

// 初始化 RTC 聊天服务 - 复用现有的 RTC 连接
const initRTCChatService = async () => {
  try {
    // 等待 RTC 连接建立
    if (!rtcManager || !rtcManager.client) {
      console.warn('RTC manager not ready, skipping chat service initialization')
      return
    }

    

    // 直接在现有的 RTC 客户端上监听数据流消息
    rtcManager.client.on("stream-message", (uid, stream) => {
      try {
        const decoder = new TextDecoder("utf-8");
        const decoded = decoder.decode(stream);
        
        // 解析协议: "message_id|part_index|total_parts|content"
        const parts = decoded.split("|");
        if (parts.length < 4) {
          console.warn('Invalid stream message format:', decoded);
          return;
        }

        const [message_id, partIndexStr, totalPartsStr, content] = parts;
        const part_index = parseInt(partIndexStr, 10);
        const total_parts = totalPartsStr === "???" ? -1 : parseInt(totalPartsStr, 10);
        // 使用全局缓存来处理消息分片
        if (!window.messageCache) {
          window.messageCache = {};
          window.messageTimeoutMap = {};
        }

        const chunk = { message_id, part_index, total_parts, content };

        // 初始化消息缓存
        if (!window.messageCache[message_id]) {
          window.messageCache[message_id] = [];
          // 设置超时清理，防止内存泄漏
          window.messageTimeoutMap[message_id] = setTimeout(() => {
            console.warn(`Message ${message_id} timeout, cleaning up`);
            delete window.messageCache[message_id];
            delete window.messageTimeoutMap[message_id];
          }, 10000); // 10秒超时
        }

        window.messageCache[message_id].push(chunk);

        // 按分片索引排序并重组当前已收到的部分
        const sorted = window.messageCache[message_id].sort((a, b) => a.part_index - b.part_index);
        const currentMessage = sorted.map(c => c.content).join("");
        
        try {
          // 尝试解析当前已收到的内容
          const decodedMessage = atob(currentMessage);
          
          const messageData = JSON.parse(decodedMessage);
          
          const { stream_id, is_final, text, text_ts, data_type } = messageData;
          
          // 判断是否为 AI 消息（stream_id 不等于当前用户 ID）
          const isAgent = Number(stream_id) !== Number(userStore.uid);
          
          // 只处理文本消息且内容不为空
          if (text && text.trim().length > 0) {
            // 根据消息类型添加不同的消息
            if (isAgent) {
              // AI 消息 - 使用流式更新（按用户维度覆盖最后一条未完成消息）
              updateStreamingMessageByUser(stream_id, text, is_final, Number(text_ts) || Date.now(), 'ai');
            } else {
              // 用户消息流式更新
              updateStreamingMessageByUser(stream_id, text, is_final, Number(text_ts) || Date.now(), 'user');
            }
          }
          
        } catch (parseError) {
          // 如果解析失败，说明消息还不完整，继续等待
        }

        // 如果收齐了所有分片，清理缓存
        if (total_parts !== -1 && window.messageCache[message_id].length === total_parts) {
          clearTimeout(window.messageTimeoutMap[message_id]);
          delete window.messageTimeoutMap[message_id];
          delete window.messageCache[message_id];
        }
      } catch (error) {
        console.error('Failed to process stream message:', error);
      }
    });

    
  } catch (error) {
    console.error('Failed to initialize RTC Chat Service:', error)
    // 聊天服务失败不影响其他功能
  }
}

// 初始化连接
const initializeConnection = async () => {
  if (isInitialized.value) {
    
      return
    }

  if (isInitializing.value) {
    
    return
  }
  
  isInitializing.value = true
  hasError.value = false
  errorMessage.value = ''
  
  try {
    
    
    // 确保用户信息完整
    if (!userStore.channelName || !userStore.uid || !userStore.requestId) {
      if (userStore.id) {
        userStore.generateTenIds(userStore.id)
      } else {
        throw new Error('用户ID不存在，请重新登录')
      }
    }

    // 并行执行不依赖的操作
    const [relativeInfoResult] = await Promise.all([
      getRelativeInfo()
      // loadChatHistory() // 暂时注释掉历史聊天记录加载
    ])
    
    // 🚀 优化：提前并行初始化 LiteAvatar 和 RTC，减少延迟
    const initPromises = []
    
    // 初始化 RTC
    initPromises.push(
      initRTC().catch(error => {
        console.warn('RTC initialization failed, continuing without RTC:', error)
        // RTC 失败不影响其他功能
      })
    )
    
    // 视频模式：并行初始化 LiteAvatar
    if (!isAudioOnly.value) {
      initPromises.push(
        initLiteAvatar().catch(error => {
          console.error('LiteAvatar initialization failed:', error)
          avatarStatusText.value = '数字人初始化失败，其他功能正常'
          // 数字人失败不影响聊天功能
        })
      )
    }
    
    // 等待 RTC 和 LiteAvatar 并行初始化完成
    await Promise.all(initPromises)
    
    // 现在启动 Agent（此时 LiteAvatar 已准备好）
    await startAgentService()
    startHeartbeat()
    // startChatHistoryPolling() // 暂时注释掉聊天记录轮询
    
    // 初始化 RTC 聊天服务（用于实时消息接收）- 在音频 RTC 之后初始化
    try {
      await initRTCChatService()
    } catch (error) {
      console.warn('RTC Chat Service initialization failed, continuing without real-time chat:', error)
      // 聊天服务失败不影响其他功能
    }
    
    // 不再注入默认欢迎消息
    
    connected.value = true
    isInitialized.value = true
    hasError.value = false
    
    
    
  } catch (error) {
    console.error('Failed to initialize connection:', error)
    errorMessage.value = '连接失败: ' + (error.message || '未知错误')
    hasError.value = true
    
    // 部分失败时尝试清理已初始化的资源
    try {
      await cleanup(false)
    } catch (cleanupError) {
      console.error('Failed to cleanup after initialization error:', cleanupError)
    }
    
    throw error
  } finally {
    isInitializing.value = false
  }
}

// ============ LiteAvatar 音频同步 ============
/**
 * 同步 RTC 音频流到 LiteAvatar
 */
const syncAudioToLiteAvatar = async () => {
  if (isAudioOnly.value) {
    console.log('🔇 Audio-only mode, skipping avatar sync')
    return
  }
  
  if (!liteAvatarClient || !liteAvatarConnected.value) {
    console.warn('⚠️ LiteAvatar not connected, cannot sync audio')
    return
  }
  
  if (!remoteAudioTrack || typeof remoteAudioTrack.getMediaStreamTrack !== 'function') {
    console.warn('⚠️ No remote audio track available')
    return
  }
  
  try {
    console.log('🎤 Starting audio sync to LiteAvatar...')
    
    // 创建音频转换器
    audioConverter = new AudioToPCMConverter(16000)
    
    // 开始处理音频 - 将 PCM 数据发送到 LiteAvatar
    // ⚠️ 架构问题：这里处理的是 Agora 的 **远程音频流**（remoteAudioTrack）
    // 应该是 AI Agent 的语音回复，而不是用户的麦克风音频
    // 
    // 正确流程应该是：
    // 1. 用户麦克风 → Agora → AI Agent (处理并生成回复)
    // 2. AI Agent 语音回复 → Agora remoteAudioTrack → 这里 → LiteAvatar
    // 3. LiteAvatar → 生成数字人视频（对应 AI 说的话）
    await audioConverter.startProcessing(
      remoteAudioTrack.getMediaStreamTrack(),
      (pcmData, isSpeechSegment) => {
        // 视频流模式只处理完整语音段
        if (!isSpeechSegment) {
          return
        }
        
        // 静音检测
        const samples = new Int16Array(pcmData)
        let maxAmplitude = 0
        for (let i = 0; i < samples.length; i++) {
          const amplitude = Math.abs(samples[i])
          if (amplitude > maxAmplitude) {
            maxAmplitude = amplitude
          }
        }
        
        const SILENCE_THRESHOLD = 500
        
        if (maxAmplitude < SILENCE_THRESHOLD) {
          return
        }
        
        // 发送完整语音段
        console.log(`Sending complete sentence: ${pcmData.byteLength} bytes`)
        if (liteAvatarClient && liteAvatarConnected.value) {
          liteAvatarClient.sendAudio(pcmData)
        }
      }
    )
    
    console.log('Audio synced to LiteAvatar')
    hasSyncedAudio.value = true
    
  } catch (error) {
    console.error('Failed to sync audio to LiteAvatar:', error)
    hasError.value = true
    errorMessage.value = '音频同步失败'
  }
}

// 初始化RTC（带重试机制）
const initRTC = async (maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
  try {
    if (!rtcManager) {
        throw new Error('rtcManager not available')
    }

    // 先获取 RTC token
    const rtcTokenResp = await getRTCToken(userStore.channelName, userStore.uid)
      const rtcToken = rtcTokenResp.data?.token
      
      if (!rtcToken) {
        throw new Error('Failed to get RTC token')
      }

    await rtcManager.init({
      appId: config.rtc.appId,
      channel: userStore.channelName,
        token: rtcToken,
      uid: parseInt(userStore.uid)
    })

    // 直接监听底层 RTC 客户端事件以获取远端音频轨
    if (rtcManager && rtcManager.client) {
      rtcManager.client.on('user-published', async (user, mediaType) => {
        try {
          await rtcManager.client.subscribe(user, mediaType)
          if (mediaType === 'audio') {
            // 抓取远端下行音频轨
              if (user && user.audioTrack && typeof user.audioTrack.getMediaStreamTrack === 'function') {
              remoteAudioTrack = user.audioTrack
              if (!isAudioOnly.value) {
                // 视频模式：由数字人处理音频，音频已包含在视频中
                // ⚠️ 注意：不需要播放 Agora 远程音频，因为视频已包含音频
                console.log('🔇 Video mode: audio playback handled by video element, not RTC')
                
                // 同步音频到 LiteAvatar
                console.log(`🔊 Audio sync check: liteAvatarConnected=${liteAvatarConnected.value}, hasSyncedAudio=${hasSyncedAudio.value}`)
                if (liteAvatarConnected.value && !hasSyncedAudio.value) {
                  console.log('🎤 Triggering audio sync to LiteAvatar...')
                  syncAudioToLiteAvatar()
                } else if (!liteAvatarConnected.value) {
                  console.warn('⚠️ LiteAvatar not connected yet, will retry sync (up to 25 times, every 200ms)')
                  // 循环重试音频同步（最多 25 次，每次间隔 200ms = 总共 5 秒）
                  let retryCount = 0
                  const maxRetries = 25
                  const retryInterval = 200 // 200 毫秒（优化延迟）
                  
                  const retryTimer = setInterval(() => {
                    retryCount++
                    console.log(`🔄 Retry attempt ${retryCount}/${maxRetries}: liteAvatarConnected=${liteAvatarConnected.value}, hasSyncedAudio=${hasSyncedAudio.value}, hasAudioTrack=${!!remoteAudioTrack}`)
                    
                    if (liteAvatarConnected.value && !hasSyncedAudio.value && remoteAudioTrack) {
                      console.log(`✅ Retry ${retryCount} succeeded! Starting audio sync...`)
                      clearInterval(retryTimer)
                      syncAudioToLiteAvatar()
                    } else if (retryCount >= maxRetries) {
                      console.error(`❌ Max retries (${maxRetries}) reached, giving up audio sync`)
                      clearInterval(retryTimer)
                    } else if (!remoteAudioTrack) {
                      console.warn(`⚠️ Retry ${retryCount}: No audio track available`)
                    } else if (hasSyncedAudio.value) {
                      console.log(`✅ Retry ${retryCount}: Audio already synced, stopping`)
                      clearInterval(retryTimer)
                    }
                  }, retryInterval)
                }
              } else {
                // 仅语音模式：确保通过 Agora 正常本地播放
                try {
                  if (typeof user.audioTrack.play === 'function') {
                    user.audioTrack.play()
                  }
                } catch(e) { console.warn('play remote audio failed:', e) }
              }
            }
          }
        } catch (e) {
          console.error('subscribe remote user failed:', e)
        }
      })

      rtcManager.client.on('user-unpublished', async (user, mediaType) => {
        // 若发布音频的用户取消发布，清掉引用
        if (mediaType === 'audio' && remoteAudioTrack && user && user.audioTrack === remoteAudioTrack) {
          remoteAudioTrack = null
        }
      })
    }

    await rtcManager.createMicrophoneAudioTrack()
    await rtcManager.join()
      
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      return
      
  } catch (error) {
      console.error(`RTC initialization attempt ${attempt} failed:`, error)
      
      if (attempt === maxRetries) {
        console.warn('RTC initialization failed after all retries, continuing without RTC functionality')
        throw error
      }
      
      // 等待后重试
      const delay = attempt * 1000
      
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }
}

// 根据性别获取语音类型
const getVoiceTypeByGender = (gender) => {
  const voiceTypes = {
    '男': 'ICL_zh_male_yangyang_v1_tob',
    '女': 'S_VYJ6wIGu1',
    'male': 'ICL_zh_male_yangyang_v1_tob',
    'female': 'S_VYJ6wIGu1',
    '1': 'ICL_zh_male_yangyang_v1_tob',
    '0': 'S_VYJ6wIGu1'
  }
  return voiceTypes[gender] || voiceTypes['男']
}

const getClusterByGender = (gender) => {
  const clusters = {
    '男': 'volcano_tts',
    '女': 'volcano_icl',
    'male': 'volcano_tts',
    'female': 'volcano_icl',
    '1': 'volcano_tts',
    '0': 'volcano_icl'
  }
  return clusters[gender] || clusters['男']
}

// 启动Agent服务
const startAgentService = async () => {
  // 再次检查必要字段
  if (!userStore.channelName || !userStore.uid || !userStore.requestId) {
    throw new Error(`缺少必要字段: channelName=${userStore.channelName}, uid=${userStore.uid}, requestId=${userStore.requestId}`)
  }
  
  try {
    // 根据角色性别选择语音类型
    const roleGender = relativeInfo.value.fullRoleData?.gender || '男'
    const voiceType = getVoiceTypeByGender(roleGender)
    const cluster = getClusterByGender(roleGender)

    console.log(`启动Agent服务 - 角色性别: ${roleGender}, 语音类型: ${voiceType}`)
    
    const response = await startAgent({
      channelName: userStore.channelName,
      graphName: "voice_assistant",
      userUid: parseInt(userStore.uid),
      voice_type: voiceType,
      cluster: cluster
    })
    if (response.code === 0 || response.code === "0") {
      // Agent服务启动成功
      console.log(`Agent服务启动成功，使用语音类型: ${voiceType}`)
    } else {
      throw new Error(response.message || 'Failed to start agent service')
    }
  } catch (error) {
    console.error('Failed to start agent service:', error)
    throw error
  }
}

// 心跳机制
const startHeartbeat = () => {
  stopHeartbeat(); // 防止重复定时器
  
  heartbeatTimer = setInterval(async () => {
    try {
      // 调用ping接口保持连接活跃
      await ping(userStore.channelName)
    } catch (error) {
      console.error('Heartbeat ping failed:', error)
    }
  }, 10000)
}

const stopHeartbeat = () => {
  if (heartbeatTimer) {
    clearInterval(heartbeatTimer)
    heartbeatTimer = null
  }
}

// 开始定时获取聊天记录
const startChatHistoryPolling = () => {
  stopChatHistoryPolling() // 防止重复定时器
  
  chatHistoryTimer = setInterval(async () => {
    try {
      await loadChatHistory()
    } catch (error) {
      console.error('Chat history polling error:', error)
    }
  }, 2000) // 每2秒调用一次
}

const stopChatHistoryPolling = () => {
  if (chatHistoryTimer) {
    clearInterval(chatHistoryTimer)
    chatHistoryTimer = null
  }
}

// 格式化时间
const formatTime = (timestamp) => {
  const date = new Date(timestamp)
  return date.toLocaleTimeString('zh-CN', { 
    hour: '2-digit', 
    minute: '2-digit' 
  })
}

// 计算属性：直接使用当前数组顺序（我们在装载与追加时已保证顺序）
const orderedMessages = computed(() => messages.value)

// 打字机效果：逐字显示AI回复
const startTypingEffect = (messageId, fullText, baseSpeed = 120) => {
  // 清除之前的定时器
  if (typingTimers.value.has(messageId)) {
    clearTimeout(typingTimers.value.get(messageId))
  }
  
  // 初始化显示文本为空
  typingMessages.value.set(messageId, '')
  
  let currentIndex = 0
  const totalLength = fullText.length
  
  const typeNextChar = () => {
    if (currentIndex < totalLength) {
      const currentText = fullText.substring(0, currentIndex + 1)
      typingMessages.value.set(messageId, currentText)
      
      // 更新对应的消息
      const messageIndex = messages.value.findIndex(msg => msg.id === messageId)
      if (messageIndex !== -1) {
        messages.value[messageIndex].displayText = currentText
        messages.value[messageIndex].isTyping = true
      } else {
        // 如果消息已被删除，停止打字机效果
        stopTypingEffect(messageId)
        return
      }
      
      currentIndex++
      
      // 根据字符类型调整速度，模拟真实说话节奏
      const currentChar = fullText[currentIndex - 1]
      let nextDelay = baseSpeed
      
      if (/[\u4e00-\u9fa5]/.test(currentChar)) {
        // 中文字符 - 稍慢，模拟中文说话节奏
        nextDelay = baseSpeed + 50
      } else if (/[a-zA-Z0-9]/.test(currentChar)) {
        // 英文字母和数字 - 稍快
        nextDelay = baseSpeed - 20
      } else if (/[，。！？；：""''（）【】]/.test(currentChar)) {
        // 标点符号 - 明显停顿，模拟说话时的停顿
        nextDelay = baseSpeed + 150
      } else if (/\s/.test(currentChar)) {
        // 空格 - 短暂停顿
        nextDelay = baseSpeed + 30
      }
      
      // 添加随机变化，让节奏更自然
      const randomVariation = Math.random() * 30 - 15 // -15 到 +15 的随机变化
      nextDelay = Math.max(nextDelay + randomVariation, 50) // 最小50ms
      
      const timer = setTimeout(typeNextChar, nextDelay)
      typingTimers.value.set(messageId, timer)
      
      // 滚动到底部，让用户看到最新内容
      nextTick(() => {
        scrollToBottom()
      })
    } else {
      // 打字完成
      const messageIndex = messages.value.findIndex(msg => msg.id === messageId)
      if (messageIndex !== -1) {
        messages.value[messageIndex].displayText = fullText
        messages.value[messageIndex].isTyping = false
      }
      
      // 清理
      typingMessages.value.delete(messageId)
      typingTimers.value.delete(messageId)
      
      // 最终滚动到底部
      nextTick(() => {
        scrollToBottom()
      })
    }
  }
  
  // 开始打字
  typeNextChar()
}

// 停止打字机效果
const stopTypingEffect = (messageId) => {
  if (typingTimers.value.has(messageId)) {
    clearTimeout(typingTimers.value.get(messageId))
    typingTimers.value.delete(messageId)
  }
  typingMessages.value.delete(messageId)
}

// 滚动到底部
const scrollToBottom = () => {
  if (messagesContainer.value) {
    // 实时消息总是滚动到底部，让用户看到最新内容
    messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
  }
}

// 处理页面可见性变化
const handleVisibilityChange = () => {
  if (document.hidden) {
    
    // 切出页面时保持对话活跃，不清理任何资源
    // 只记录日志，让用户知道页面已隐藏但对话继续
  } else {
    
    // 页面重新可见时，确保所有功能正常
  }
}

// 处理浏览器关闭/刷新
const handleBeforeUnload = (event) => {
  emergencyStopAudio()
  cleanup()
}

// 紧急停止所有音频
const emergencyStopAudio = () => {
  try {
    if (window.AudioContext || window.webkitAudioContext) {
      const audioContext = new (window.AudioContext || window.webkitAudioContext)()
      audioContext.suspend()
    }
    
    navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
      stream.getTracks().forEach(track => track.stop())
    }).catch(() => {})
  } catch (error) {
    console.error('Emergency audio stop failed:', error)
  }
}

// 组装头像为可用的 Data URL（参考 index 的实现）
const buildAvatarDataUrl = (profile) => {
  if (!profile) return '/static/images/p1.png'
  const fileBase64 = profile.fileBase64
  if (!fileBase64) return '/static/images/p1.png'
  if (fileBase64.startsWith('data:')) return fileBase64
  const mimeType = profile.mimeType || 'image/png'
  return `data:${mimeType};base64,${fileBase64}`
}

// 根据当前选中的亲属ID加载详情；若无ID则尝试使用列表第一项
const getRelativeInfo = async () => {
  try {
    // 优先使用路由传递的完整role数据
    if (route.query.avatar && route.query.relativeId) {
      // 从query参数构建完整的role数据对象
      const fullRoleData = {
        relativeId: route.query.relativeId || '',
        userId: route.query.userId || '',
        relationship: route.query.roleRelationship || '',
        name: route.query.roleName || '亲属',
        gender: route.query.gender || '',
        age: route.query.age || '',
        birthDate: route.query.birthDate || '',
        hometown: route.query.hometown || '',
        educationLevel: route.query.educationLevel || '',
        livecity: route.query.livecity || '',
        remark: route.query.remark || '',
        createBy: route.query.createBy || '',
        createTime: route.query.createTime || '',
        updateBy: route.query.updateBy || '',
        updateTime: route.query.updateTime || '',
        personalityProfile: null, // query中不传递文件数据
        audioRecordings: null,
        tempRelativeId: route.query.tempRelativeId || ''
      }

      relativeInfo.value = {
        name: route.query.roleName || '亲属',
        relationship: route.query.roleRelationship || '家人',
        avatar: route.query.avatar,
        fullRoleData: fullRoleData
      }

      // 设置当前亲属ID
      const rid = route.query.relativeId
      if (rid) {
        relativeStore.setCurrentRelativeId(rid)
      }

      console.log('从query参数获取到完整role数据:', fullRoleData)
      return
    }
    
    let rid = relativeStore.currentRelativeId
    if (!rid) {
      // 如还未选择，尝试使用列表首项作为回退
      if (!relativeStore.list || relativeStore.list.length === 0) {
        await relativeStore.fetchList(userStore.id)
      }
      rid = relativeStore.list?.[0]?.id ?? relativeStore.list?.[0]?.relativeId ?? relativeStore.list?.[0]?.rid
      if (rid) {
        relativeStore.setCurrentRelativeId(rid)
      }
    }

    if (!rid) {
      // 仍然没有，保持默认占位
      relativeInfo.value = {
        name: '亲属',
        relationship: '家人',
        avatar: '/static/images/p1.png',
        fullRoleData: null
      }
      return
    }

    // 拉取详情
    const res = await getRelativeProfile(rid)
    const data = res?.data || {}
    let avatarUrl = '/static/images/p1.png'
    // personalityProfile 可能是数组
    if (Array.isArray(data.personalityProfile) && data.personalityProfile.length > 0) {
      avatarUrl = buildAvatarDataUrl(data.personalityProfile[0])
    } else if (typeof data.personalityProfile === 'object' && data.personalityProfile !== null) {
      avatarUrl = buildAvatarDataUrl(data.personalityProfile)
    } else if (data.avatar) {
      avatarUrl = data.avatar
    }

    relativeInfo.value = {
      name: data.name || '亲属',
      relationship: data.relationship || '家人',
      avatar: avatarUrl,
      fullRoleData: data
    }
  } catch (e) {
    console.warn('加载亲属信息失败，使用默认占位:', e)
    relativeInfo.value = {
      name: '亲属',
      relationship: '家人',
      avatar: '/static/images/p1.png',
      fullRoleData: null
    }
  }
}

// 切换麦克风
const toggleMicrophone = async () => {
  if (!rtcManager) {
    console.warn('RTC not available')
    return
  }

  try {
    if (microphoneEnabled.value) {
      await rtcManager.muteLocalAudioTrack()
    } else {
      await rtcManager.unmuteLocalAudioTrack()
    }
    microphoneEnabled.value = !microphoneEnabled.value
  } catch (error) {
    console.error('Failed to toggle microphone:', error)
  }
}

// 处理侧边栏标签切换
const handleTabChange = (tabName) => {
  // 静默处理标签切换
}

// 结束通话
const endCall = async () => {
  try {
    await cleanup()
  go('/pages/pc/index')
  } catch (error) {
    console.error('End call error:', error)
  go('/pages/pc/index')
  }
}

// 清理资源
const cleanup = async (stopHeartbeatFlag = true) => {
  // 防止重复清理
  if (isCleaningUp.value) {
    
    return
  }
  
  isCleaningUp.value = true
  
  try {
    
    
    // 只有在真正离开页面时才停止心跳
    if (stopHeartbeatFlag) {
    stopHeartbeat()
    }
    
    // 停止聊天记录轮询
    // stopChatHistoryPolling() // 暂时注释掉聊天记录轮询停止
    
    // ============ 清理 LiteAvatar ============
    if (liteAvatarClient) {
      try {
        console.log('🧹 Cleaning up LiteAvatar...')
        
        // 🚀 清理 Object URL（防止内存泄漏）
        if (currentFrameData.value && currentFrameData.value.startsWith('blob:')) {
          URL.revokeObjectURL(currentFrameData.value)
          currentFrameData.value = ''
        }
        
        // 🎬 清理视频URL（防止内存泄漏）
        const video = avatarVideo.value
        if (video && typeof video.getAttribute === 'function') {
          const oldSrc = video.src || video.getAttribute('src')
          if (oldSrc && oldSrc.startsWith('blob:')) {
            URL.revokeObjectURL(oldSrc)
            video.src = ''
            video.removeAttribute('src')
          }
        }
        
        // 清理待机视频URL
        if (idleVideoUrl.value && idleVideoUrl.value.startsWith('blob:')) {
          URL.revokeObjectURL(idleVideoUrl.value)
          idleVideoUrl.value = ''
        }
        
        // 清理语音视频URL
        if (currentSpeechVideoUrl && currentSpeechVideoUrl.startsWith('blob:')) {
          URL.revokeObjectURL(currentSpeechVideoUrl)
          currentSpeechVideoUrl = null
        }
        
        isPlayingIdleVideo.value = false
        isPlayingSpeechVideo = false
        console.log('✅ Video resources cleared')
        
        // 刷新剩余音频数据
        // liteAvatarClient.flush()

        // 重置缓冲区
        liteAvatarClient.reset()
        
        // 断开连接
        liteAvatarClient.disconnect()
        liteAvatarClient = null
        
        console.log('✅ LiteAvatar cleaned up')
      } catch (error) {
        console.error('❌ Failed to cleanup LiteAvatar:', error)
      }
    }
    
    // 清理音频转换器
    if (audioConverter) {
      try {
        console.log('🧹 Stopping audio converter...')
        audioConverter.stop()
        audioConverter = null
        console.log('✅ Audio converter stopped')
      } catch (error) {
        console.error('❌ Failed to stop audio converter:', error)
      }
    }
    
    // 清理 RTC 聊天服务（清理全局缓存）
    try {
      
      if (window.messageTimeoutMap) {
        Object.values(window.messageTimeoutMap).forEach(timeout => clearTimeout(timeout));
        window.messageCache = {};
        window.messageTimeoutMap = {};
      }
      
    } catch (error) {
      console.error('Failed to cleanup RTC Chat Service:', error)
    }

    // 清理打字机效果定时器
    try {
      typingTimers.value.forEach(timer => clearTimeout(timer))
      typingTimers.value.clear()
      typingMessages.value.clear()
    } catch (error) {
      console.error('Failed to cleanup typing effects:', error)
    }

    // 清理 RTC
    if (rtcManager && connected.value) {
      try {
        
        await rtcManager.leave()
        
      } catch (error) {
        console.error('Failed to leave RTC:', error)
      }
    }
    
    // 停止 Agent 服务
    if (connected.value) {
      try {
        
        const response = await stopAgent(userStore.channelName)
        
      } catch (error) {
        console.error('Failed to stop agent service:', error)
      }
    }
    
    // 重置状态
    connected.value = false
    liteAvatarConnected.value = false
    isInitialized.value = false
    messages.value = []
    isAITyping.value = false
    errorMessage.value = ''
    hasSyncedAudio.value = false
    currentFrameData.value = ''
    
  } catch (error) {
    console.error('Cleanup error:', error)
    connected.value = false
  } finally {
    isCleaningUp.value = false
  }
}

// 获取历史聊天记录
const loadChatHistory = async () => {
  try {
    // 获取当前选中的亲属ID，如果没有则使用0（AI对话）
    const currentRelativeId = relativeStore.currentRelativeId || 0
    
    const response = await getChatHistory(userStore.id, currentRelativeId)
    
         if (response && response.code === 200 && response.rows && response.rows.length > 0) {
       // 按 id 升序（id 越大越新 → 新消息在数组底部）
       const rowsSorted = [...response.rows].sort((a, b) => (a.id || 0) - (b.id || 0))
       // 将API返回的数据转换为组件需要的格式
       const historyData = rowsSorted.map((item, index) => {
         // 检查字段是否存在且不为空
         if (!item.userMessage || !item.aiResponse) {
           console.warn(`Item ${index} has empty message: userMessage="${item.userMessage}", aiResponse="${item.aiResponse}"`)
         }
         
         // 使用索引来确保正确的顺序，而不是依赖时间戳
         // 递增时间戳，保证顺序稳定（旧在上，新在下）
         const baseTimestamp = Date.now() + index
         
         const userMessage = {
           text: item.userMessage || '用户消息为空',
           displayText: item.userMessage || '用户消息为空',
           type: 'user',
           isFinal: true,
           timestamp: baseTimestamp,
           id: `user-${item.id}-${index}`,
           isTyping: false
         }
         
         const aiMessage = {
           text: item.aiResponse || 'AI回复为空',
           displayText: '', // 历史AI消息也使用打字机效果
           type: 'ai',
           isFinal: true,
           timestamp: baseTimestamp + 1000, // AI回复稍晚一点
           id: `ai-${item.id}-${index}`,
           isTyping: true
         }
         
         return [userMessage, aiMessage]
       }).flat() // 不再额外排序，保持我们赋值顺序（旧->新）
       
       messages.value = historyData
       
       // 为历史AI消息启动打字机效果
       nextTick(() => {
         messages.value.forEach((message, index) => {
           if (message.type === 'ai' && message.text && message.text.trim().length > 0) {
             // 延迟启动，让消息按顺序显示
             setTimeout(() => {
               startTypingEffect(message.id, message.text, 120)
             }, index * 100) // 每条消息间隔100ms启动
           }
         })
       })
     } else {
       messages.value = []
     }
     
     // 历史记录加载完成后滚动到底部
     nextTick(() => {
       scrollToBottom()
     })
    
  } catch (error) {
    console.error('Failed to load chat history:', error)
    messages.value = []
  }
}

// 流式消息更新（按用户维度覆盖最后一条未完成消息，符合参考实现）
const updateStreamingMessageByUser = (userId, text, isFinal, time, role = 'ai') => {
  // 找到同一用户最后一条"已最终"和"未最终"的消息
  let lastFinalIndex = -1
  let lastNonFinalIndex = -1
  for (let i = messages.value.length - 1; i >= 0; i--) {
    const item = messages.value[i]
    if (!item) continue
    if (role === 'ai' && item.type !== 'ai') continue
    if (role === 'user' && item.type !== 'user') continue
    if (item.userId == userId && item.isFinal && lastFinalIndex === -1) {
      lastFinalIndex = i
    }
    if (item.userId == userId && !item.isFinal && lastNonFinalIndex === -1) {
      lastNonFinalIndex = i
    }
    if (lastFinalIndex !== -1 && lastNonFinalIndex !== -1) break
  }

  const messageId = `${role}-${userId}-${Date.now()}`
  const payload = {
    text,
    displayText: role === 'ai' ? '' : text, // AI消息初始为空，用户消息直接显示
    type: role === 'user' ? 'user' : 'ai',
    isFinal: !!isFinal,
    timestamp: Number(time) || Date.now(),
    id: messageId,
    userId,
    isTyping: role === 'ai' ? true : false, // AI消息初始为打字状态
    isWaitingVideo: role === 'ai' && isFinal ? true : false // AI消息完成时等待视频合成
  }

  if (lastFinalIndex !== -1) {
    // 有最终消息：如果存在未最终，用它覆盖；否则新起一条
    if (lastNonFinalIndex !== -1) {
      // 停止之前的打字效果
      stopTypingEffect(messages.value[lastNonFinalIndex].id)
      messages.value[lastNonFinalIndex] = payload
    } else {
      messages.value.push(payload)
    }
  } else {
    // 没有最终消息：若有未最终则覆盖，否则作为第一条
    if (lastNonFinalIndex !== -1) {
      // 停止之前的打字效果
      stopTypingEffect(messages.value[lastNonFinalIndex].id)
      messages.value[lastNonFinalIndex] = payload
    } else {
      messages.value.push(payload)
    }
  }

  // 保持时间顺序
  messages.value.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0))

  // 视频模式：等待视频播放时再显示文字

  nextTick(() => {
    scrollToBottom()
  })
}

// 添加消息到聊天记录
const addMessage = (text, type) => {
  const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  const message = {
    text,
    displayText: type === 'ai' ? '' : text, // AI消息初始为空，用户消息直接显示
    type,
    isFinal: true,
    timestamp: Date.now(),
    id: messageId,
    isTyping: type === 'ai' ? true : false,
    isWaitingVideo: type === 'ai' ? true : false // AI消息等待视频合成
  }
  messages.value.push(message)
  
  // 视频模式：等待视频播放时再显示文字
  
  // 实时消息滚动到底部
  nextTick(() => {
    scrollToBottom()
  })
}



// 重试初始化连接
const retryConnection = async () => {
  if (isInitializing.value) return
  
  try {
    
    hasError.value = false
    errorMessage.value = ''
    await initializeConnection()
    } catch (error) {
    console.error('Retry failed:', error)
    hasError.value = true
  }
}

// 组件生命周期
onMounted(async () => {
  try {
  // 🎯 优先从路由参数读取角色头像，避免显示默认 p1.png
  if (route.query.avatar) {
    relativeInfo.value.avatar = route.query.avatar
    relativeInfo.value.name = route.query.roleName || '亲属'
    relativeInfo.value.relationship = route.query.roleRelationship || '家人'
    console.log('✅ 从路由参数加载角色头像:', route.query.avatar)
  }
  
  // 先读取路由参数，设定通话类型；若缺失则从 sessionStorage 兜底
  const fromRoute = route.query.callType
  if (fromRoute && String(fromRoute).trim().length > 0) {
    callType.value = String(fromRoute)
  } else {
    try { callType.value = String(sessionStorage.getItem('callType') || 'audio') } catch (e) { callType.value = 'audio' }
  }
  
  await initializeConnection()
    
    // 监听当前选择的亲属ID变化
  watch(() => relativeStore.currentRelativeId, async (newVal, oldVal) => {
    if (newVal && newVal !== oldVal) {
      await getRelativeInfo()
        // await loadChatHistory() // 暂时注释掉历史聊天记录加载
    }
  })
  
    // 注册事件监听器
  document.addEventListener('visibilitychange', handleVisibilityChange)
  window.addEventListener('beforeunload', handleBeforeUnload)
    
    // 已改为直接监听底层 user-published 获取远端音频轨，这里不再轮询
    
  } catch (error) {
    console.error('Component mount failed:', error)
    // 显示用户友好的错误提示
    errorMessage.value = '页面初始化失败，请刷新重试'
  }
})

onBeforeUnmount(() => {
  // 移除事件监听器
  document.removeEventListener('visibilitychange', handleVisibilityChange)
  window.removeEventListener('beforeunload', handleBeforeUnload)
  if (remoteAudioPollTimer) { clearInterval(remoteAudioPollTimer); remoteAudioPollTimer = null }
})

onBeforeRouteLeave((to, from, next) => {
  // 只有在真正离开页面时才清理资源
  // 切出页面（如切换到其他标签页）时不清理
  if (connected.value && !isCleaningUp.value) {
    
    cleanup(true).then(() => {
      next()
    }).catch((error) => {
      console.error('Cleanup error during route leave:', error)
      next() // 即使清理失败也要允许路由跳转
    })
  } else {
    next()
  }
})

onUnmounted(() => {
  // 组件卸载时确保清理完成
  if (!isCleaningUp.value) {
  cleanup()
  }
})

watch(expanded, (val) => {
  // 展开时滚动到底部，收起时也滚动到底部（保持一致性）
  nextTick(() => {
    scrollToBottom()
  })
})
</script>

<style scoped>
/* 主容器样式 */
.layout-container {
  display: flex;
  height: 100vh;
  background-color: #1976ed;
  border-radius: 0;
  overflow: hidden;
  position: relative;
}

/* 主内容区样式 */
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 24px 0 0 24px;
  padding: 0;
  overflow: hidden;
}

.main-header {
  height: 64px;
  display: flex;
  align-items: center;
  padding-left: 32px;
  background: #fff;
  font-size: 28px;
  font-weight: 600;
  border-bottom: 1px solid #f0f0f0;
  flex-shrink: 0;
}

.main-title {
  color: #222;
}

.main-body {
  flex: 1;
  background: #fff;
  padding: 20px 32px;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
  min-height: 0;
}

/* 数字人显示区域 */
.digital-human-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 20px;
  z-index: 1; /* 确保在聊天记录下方 */
}

.avatar-container {
  width: 320px;
  height: 320px;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
  position: relative;
  background-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 视频准备好后显示黑色背景 */
.avatar-container.video-ready {
  background-color: #333;
}

.digital-human-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  background-color: #333;
  display: block;
  position: relative;
  z-index: 3;
  transition: opacity 0.3s ease;
}

/* 隐藏视频控制条和进度条 */
video::-webkit-media-controls {
  display: none !important;
}

video::-webkit-media-controls-enclosure {
  display: none !important;
}

video::-webkit-media-controls-panel {
  display: none !important;
}

video::-webkit-media-controls-play-button {
  display: none !important;
}

video::-webkit-media-controls-timeline {
  display: none !important;
}

video::-webkit-media-controls-current-time-display {
  display: none !important;
}

video::-webkit-media-controls-time-remaining-display {
  display: none !important;
}

video::-webkit-media-controls-mute-button {
  display: none !important;
}

video::-webkit-media-controls-volume-slider {
  display: none !important;
}

video::-webkit-media-controls-fullscreen-button {
  display: none !important;
}

/* 为 avatarVideo 额外确保没有控制器 */
.avatar-container video {
  pointer-events: none; /* 防止点击触发控制器 */
}

/* 头像占位符 - 避免闪现 */
.avatar-placeholder {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  transition: opacity 0.3s ease;
}

.placeholder-text {
  color: white;
  font-size: 14px;
  text-align: center;
  padding: 0 20px;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

/* 视频准备好后隐藏占位符 */
.avatar-container.video-ready .avatar-placeholder {
  opacity: 0;
  pointer-events: none;
}

/* 视频加载状态 */
.avatar-container.show-loader::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  border: 3px solid #f3f3f3;
  border-top: 3px solid #1976ed;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  z-index: 0;
}

.avatar-container.show-loader.video-loaded::before {
  display: none;
}

/* 音频模式不显示加载指示器 */
.avatar-container.audio-only::before {
  display: none;
}

@keyframes spin {
  0% { transform: translate(-50%, -50%) rotate(0deg); }
  100% { transform: translate(-50%, -50%) rotate(360deg); }
}

/* 连接状态指示器 */
.connection-status {
  position: absolute;
  bottom: -40px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 8px 16px;
  border-radius: 20px;
  font-size: 14px;
  z-index: 10;
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #ff4757;
  animation: pulse 2s infinite;
}

.connection-status.connected .status-dot {
  background-color: #2ed573;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.status-text {
  font-size: 12px;
  white-space: nowrap;
}

/* 聊天记录外框 */
.chat-history {
  width: 100%;
  max-width: none; /* 移除最大宽度限制 */
  background: #f5f8ff;
  border-radius: 18px;
  padding: 32px 24px 24px 24px;
  min-height: 200px; /* 恢复原始最小高度 */
  position: relative;
  margin: 0 auto 32px auto; /* 恢复原始边距 */
  box-shadow: none;
  max-height: 400px; /* 恢复原始最大高度 */
  overflow-y: hidden;
  transition: all 0.3s cubic-bezier(.4,0,.2,1);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  z-index: 10;
}

.chat-history.expanded {
  position: relative;
  margin-top: -200px; /* 恢复原始向上移动距离 */
  max-height: 80vh; /* 恢复原始最大高度 */
  overflow: hidden;
  z-index: 20;
}

.chat-history.expanded .messages-container {
  overflow-y: auto;
  max-height: calc(80vh - 80px); /* 恢复原始高度计算 */
}

.digital-human-section {
  z-index: 1;
}

/* 消息列表 */
.messages-container {
  display: flex;
  flex-direction: column;
  gap: 16px;
  height: 100%;
  overflow-y: hidden; /* 默认不滚动 */
  justify-content: flex-end; /* 默认显示底部最新消息 */
}

/* 展开时允许滚动 */
.chat-history.expanded .messages-container {
  overflow-y: auto;
  max-height: calc(80vh - 80px);
  justify-content: flex-start;
}

/* 单条消息 */
.message {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: none;
  box-shadow: none;
  padding: 0;
  flex-shrink: 0; /* 防止消息被压缩 */
}

/* 消息前的小圆点 */
.message-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 7px;
  flex-shrink: 0;
}
.message.user .message-dot {
  background: #ff9800;
}
.message.ai .message-dot {
  background: #1976ed;
}

/* 消息文本 */
.message-content {
  font-size: 16px;
  color: #222;
  background: none;
  padding: 0;
  border-radius: 0;
  line-height: 1.7;
  word-break: break-all;
}

/* 时间可隐藏或微灰色小字 */
.message-time {
  font-size: 12px;
  color: #b0b0b0;
  margin-left: 8px;
  margin-top: 2px;
}

/* 聊天记录区域优化 */
.chat-history {
  width: 100%;
  height: 300px;
  background: #f8f9fa;
  border-radius: 12px;
  margin: 20px 0 40px 0;
  position: relative;
  z-index: 1;
  overflow: hidden; /* 确保内容不会溢出到框外 */
}

/* 滚动内容区域 */
.chat-content {
  height: 100%;
  padding: 20px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  box-sizing: border-box; /* 确保padding计算在内 */
}

/* 展开状态 */
.chat-history.expanded {
  height: 500px;
  margin-top: -200px;
  margin-bottom: 40px;
  z-index: 10;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

.chat-history.expanded .chat-content {
  overflow-y: auto;
  justify-content: flex-start;
}

/* 折叠状态的渐变遮罩 - 改为顶部遮罩 */
.chat-history:not(.expanded)::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 40px;
  background: linear-gradient(#f8f9fa, transparent);
  pointer-events: none;
  z-index: 5; /* 确保遮罩在内容上方但在按钮下方 */
}

/* 按钮组样式 */
.chat-controls {
  position: absolute;
  top: 15px;
  right: 20px;
  display: flex;
  gap: 10px;
  z-index: 30;
}

/* 展开对话按钮 */
.expand-toggle {
  background: #1976ed;
  color: white;
  border: none;
  border-radius: 20px;
  padding: 8px 16px;
  font-size: 12px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.expand-toggle:hover {
  background: #1565c0;
}



/* 测试按钮样式 */
.test-btn {
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 20px;
  padding: 8px 16px;
  font-size: 12px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.test-btn:hover {
  background: #45a049;
}

.chat-item {
  display: flex;
  align-items: flex-start;
  margin-bottom: 16px;
  gap: 12px;
  animation: fadeIn 0.3s ease-in;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.message-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.message-text {
  font-size: 16px;
  color: #333;
  line-height: 1.5;
  word-wrap: break-word;
}

.message-time {
  font-size: 12px;
  color: #999;
  margin-top: 4px;
}

.user-message {
  flex-direction: row;
}

.ai-message {
  flex-direction: row;
}

.user-message .message-text {
  color: #333;
  background: transparent;
  padding: 0;
  border-radius: 0;
}

.ai-message .message-text {
  color: #333;
  background: transparent;
  padding: 0;
  border-radius: 0;
  border: none;
}

/* 正在输入指示器 */
.typing-indicator {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* 视频合成加载动画 */
.video-loading {
  display: inline-flex;
  align-items: center;
  padding: 4px 0;
}

.video-loading .loading-dots {
  display: flex;
  align-items: center;
  gap: 6px;
}

.video-loading .loading-dots span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #1976ed;
  animation: dotFlashing 1.4s infinite ease-in-out;
}

.video-loading .loading-dots span:nth-child(1) {
  animation-delay: 0s;
}

.video-loading .loading-dots span:nth-child(2) {
  animation-delay: 0.2s;
}

.video-loading .loading-dots span:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes dotFlashing {
  0%, 80%, 100% {
    opacity: 0.2;
    transform: scale(0.7);
  }
  40% {
    opacity: 1;
    transform: scale(1.1);
  }
}

.typing-text {
  font-size: 14px;
  color: #666;
  font-style: italic;
}

/* 打字机光标效果 */
.typing-cursor {
  display: inline-block;
  width: 2px;
  height: 1.2em;
  background-color: #1976ed;
  margin-left: 2px;
  animation: blink 1s infinite;
  vertical-align: text-bottom;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

/* 空状态优化 */
.empty-tip {
  text-align: center;
  color: #666;
  padding: 60px 20px;
}

.empty-subtitle {
  font-size: 14px;
  color: #999;
  margin-top: 8px;
}

/* 消息操作按钮 */
.message-action {
  font-size: 12px;
  color: #1976ed;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  transition: background-color 0.2s;
}

.message-action:hover {
  background-color: #e3f2fd;
}

/* 滚动条样式 */
.chat-history::-webkit-scrollbar {
  width: 6px;
}

.chat-history::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 3px;
}

.chat-history::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 3px;
}

.chat-history::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}

/* 控制按钮区域 - 固定在底部 */
.controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: auto;
  margin-bottom: 16px;
  flex-shrink: 0;
}

/* 控制按钮样式 */
.control-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.2s;
}

.end-call-btn {
  background-color: #ff4757;
  color: white;
}

.end-call-btn:hover {
  transform: scale(1.05);
  background-color: #ff3742;
}

/* 背景色类 */
.bg-blue-600 { background-color: #2563eb; }
.bg-red-600 { background-color: #dc2626; }
.hover\:bg-blue-700:hover { background-color: #1d4ed8; }
.hover\:bg-red-700:hover { background-color: #b91c1c; }

/* 连接状态指示器 */
.w-2 { width: 8px; }
.h-2 { height: 8px; }
.rounded-full { border-radius: 50%; }
.bg-green-500 { background-color: #10b981; }
.bg-red-500 { background-color: #ef4444; }
.text-sm { font-size: 14px; }
.text-gray-600 { color: #6b7280; }
.flex { display: flex; }
.items-center { align-items: center; }
.gap-2 { gap: 8px; }
.mt-2 { margin-top: 8px; }

/* 消息透明度 */
.opacity-70 { opacity: 0.7; }

/* 空状态提示 */
.empty-tip {
  text-align: center;
  color: #666;
  padding: 40px 20px;
}

/* 工具类 */
.w-full { width: 100%; }
.h-full { height: 100%; }
.bg-gray-800 { background-color: #1f2937; }
.justify-center { justify-content: center; }
.text-center { text-align: center; }
.overflow-hidden { overflow: hidden; }

/* 消息点样式 */
.message-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-top: 6px;
}

.user-dot {
  background-color: #ff9800;
}

.ai-dot {
  background-color: #2196f3;
}

.digital-human-video {
  width: 280px;
  height: 280px;
  object-fit: cover;
  border-radius: 50%;
  background-color: #333;
  display: block;
  position: relative;
  z-index: 1;
}



/* 错误状态样式 */
.error-status {
  margin-top: 20px;
  text-align: center;
  padding: 16px;
  background: rgba(255, 71, 87, 0.1);
  border: 1px solid rgba(255, 71, 87, 0.3);
  border-radius: 12px;
  max-width: 400px;
}

.error-message {
  color: #ff4757;
  font-size: 14px;
  margin-bottom: 12px;
  line-height: 1.4;
}

.retry-btn {
  background: #ff4757;
  color: white;
  border: none;
  border-radius: 20px;
  padding: 8px 20px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s;
}

.retry-btn:hover:not(:disabled) {
  background: #ff3742;
  transform: translateY(-1px);
}

.retry-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
  transform: none;
}
</style>









